--[[
Name: BrownieHelper
Developed by: Sonora (The Dragon Flight)
Website: http://sonora.kirintor.googlepages.com/contents
SVN: http://code.google.com/p/browniehelper/source/browse
License: GNU General Public License v3
]]

--[[
All of the chat/AceComm goodies for BrownieHelper are factored out here for
readability and because I have a sense one day this will be a neat and
modularized little library on its own... I just don't know what it looks like
yet.  But alas, I do know what it will be called...

1)  all commands take and return only one argument: args
2)  don't start your commands with lower case letters, they should all be
considered reserved
]]

local __version__ = "1.0"

--For the prototype design we'll just hash everything onto BrownieHelper it self...--
function BrownieHelper:setupCommunication()

    --Set a comm prefix for AceComm
    self:SetCommPrefix("BrownieHelper")
    
    --Memoize the allowed commands
    local memoizations = {}
    for name, handler in pairs(self.commands) do
        table.insert(memoizations, "request"..name)
        table.insert(memoizations, "respond"..name)
    end
    self:RegisterMemoizations(memoizations)
    
    --Setup the communications over whispers
    self:RegisterComm(self.commPrefix, "WHISPER", "receiveCommand")
    
end

--Application API
function BrownieHelper:registerCommand(name, requestHandler, responseHandler)
    --[[
    Protocol for commands that anticipate a single call and response round
    between two clients.
        
        name = str
        --the name of the command to register
        
        requestHander(sender, args, responsecall(args))
        --this will be called locally when a foreign client calls the command on
        us
        
        responseHandler(sender, args)
        --this will be called locally when a server we've contacted reponds with
        the result of our call.
        
        returns initiateCommand(recipient, args)
        --a callable that will initiate the command sequence with the listed
        recipient
        
        By default self:call'Command'(recipient, args) will be mapped to
        initiateCommand
        
    ]]
    
    --If this is the first time we're called
    if self.commands==nil then self.commands = {} end
    self.commands[name] = {respond=responseHandler,request=requestHandler}
    
    --Setup the calling sugar
    local function initiateCommand(recipient, args) self:sendCommand(recipient, "request"..name, args) end
    
    self["call"..name] = function(self, recipient, args) initiateCommand(recipient, args) end
    return initiateCommand
    
end

function BrownieHelper:registerDispatchingCommand(name, responseHandler, clientFilter)
    --[[
    Protocol for commands that anticipate a single reguest (client) and multiple
    possible response dispatches (server).  Server code doesn't have to worry
    about handling new incomming client requests, as we cache the last result.
    
    Clients can call:
        registerForCommand(server, command) and
        unregisterForCommand(server, command)
        
    ...to add or remove themselves from the dispatch list.
        
    Servers can call:
        unregisterClients(command)
        
    ...to clear all clients (also clears the result cache).
        
    Arguments
    
        name = str
        --the name of the command to register
        
        responseHandler(sender, args)
        --this will be called locally when a server we've contacted reponds with
        the result of our call.
        
        clientFilter(client)
        --Optional argument that will return true or false to indicate whether
        the client can included in the dispatch (checked at time of dispatch)
        
        returns dispatchCommand(args)
        --a callable which can be called by the server to update all registered
        clients
        
        By default:
        
        self:call'Command'(recipient, args) will be mapped to initiateCommand
        for clients to call.
        
        self:dispatch'Command'(args) will be mapped to dispatchCommand for
        server code to call.
        
    ]]
    
    --If this is the first time we're called
    if self.commands==nil then self.commands = {} end
    
    --Create a custom request handler
    local function requestHandler(sender, args)
        local command = self.commands[name]
        
        if args.registerCommand == true then
            --Register the client
            command.clients[sender] = true
            
            --Respond with the cache of the last result
            if command.clientFilter(sender) and command.lastResult~=nil then self:sendCommand(sender, "respond"..name, command.lastResult) end
            
        else
            --unregister the client
            command.clients[sender] = nil
            
        end
    end
    
    --Add in the command
    self.commands[name] = {respond=responseHandler, request=requestHandler, lastResult=nil, clients={}, clientFilter=(clientFilter or function() return true end)}
    
    --Setup client sugar
    self["call"..name] = function(self, recipient) self:sendCommand(recipient, "request"..name) end
    
    --Setup server sugar
    local function dispatchCommand(args)
        local command = self.commands[name]
        --DEBUG:  pp("dispatching "..name.." to clients")
        for client,registered in pairs(command.clients) do
            if registered==true and command.clientFilter(client)==true then self:sendCommand(client, "respond"..name, args) end
        end
        command.lastResult=args
    end
    
    self["dispatch"..name] = function(self, args) dispatchCommand(args) end
    return dispatchCommand
    
end
    
function BrownieHelper:registerForCommand(server, name)
    self:sendCommand(server, "request"..name, {registerCommand=true})
end

function BrownieHelper:unregisterForCommand(server, name)
    self:sendCommand(server, "request"..name, {registerCommand=false})
end

function BrownieHelper:unregisterClients(name)
    --if no name is provided we unregister everything
    
    if name==nil then
        for name, command in pairs(self.commands) do self:unregisterClients(name) end
    else
        --just clear out the hash.
        self.commands[name].clients = {}
        self.commands[name].lastResult=nil 
    end
    
end

--Top level communication handlers
function BrownieHelper:receiveCommand(prefix, sender, distribution, commandstr, args)

    --DEBUG: pp("message recieved "..commandstr.." from "..sender)
    
    local commtype, command
    
    --See if its a request
    _,_,command = string.find(commandstr, "request(%a+)")
    if command~=nil then
        commtype = "request"
    else
        --see if its a response
        _,_,command = string.find(commandstr, "respond(%a+)")
        if command~=nil then
            commtype="respond"
        end
    end
    
    handlers = self.commands[command]
    if handlers~=nil then
        local currentHandler = handlers[commtype]
        local responseCall = function(args) self:sendCommand(sender, "respond"..command, args) end
        
        if type(currentHandler) == "function" then currentHandler(sender,args,responseCall)
        elseif type(currentHandler) == "string" then self[currentHandler](self, sender,args,responseCall)
        else
            if self[commtype..command]~=nil then self[commtype..command](self, sender,args,responseCall)
            else pp("Recieved unknown command '"..tostring(commandstr).."' from "..sender) end
        end
        
    else    
        --For debugging
        pp("Recieved unknown command '"..tostring(commandstr).."' from "..sender)
        
    end
end


function BrownieHelper:sendCommand(recipient, commandstr, args)

    --make sure the unit exists (note asking if they are connected is the most meaningful way to query existence... it turns out)
    if not UnitIsConnected(recipient) then return false end
    
    --Debug: pp("message sent "..commandstr.. " to "..recipient.." with "..tostring(args))
    self:SendCommMessage("WHISPER", recipient, commandstr, args)
    
end
